Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transport,grpc: Integrate delegating resolver and introduce dial options for target host resolution #7881

Open
wants to merge 55 commits into
base: master
Choose a base branch
from

Conversation

eshitachandwani
Copy link
Member

@eshitachandwani eshitachandwani commented Nov 29, 2024

Fixes: #7556

  • Remove environment variable detection during transport creation. Introduce a per-address attribute to specify the proxy CONNECT string. This change aligns with the xDS design and provides more control over proxy behavior.
  • Create a new resolver that handles proxy configuration and delegates to child resolvers for target and proxy address resolution.
  • Introduce a new DialOption to preserve the current behavior of grpc.Dial, where the resolved target address is used in the proxy CONNECT request. This option ensures backward compatibility for users relying on the existing behavior.

RELEASE NOTES:

  • Target resolution is no longer performed on the client when using default resolvers with grpc.NewClient in conjunction with proxy environment variables.
  • New WithTargetResolutionEnabled() dial option added to explicitly force target URI resolution on the client.
  • When non-default resolvers are used alongside proxy environment variables, target resolution is now performed on the client.

@eshitachandwani eshitachandwani added the Area: Client Includes Channel/Subchannel/Streams, Connectivity States, RPC Retries, Dial/Call Options and more. label Nov 29, 2024
@eshitachandwani eshitachandwani added this to the 1.69 Release milestone Nov 29, 2024
Copy link

codecov bot commented Nov 29, 2024

Codecov Report

Attention: Patch coverage is 86.55462% with 16 lines in your changes missing coverage. Please review.

Project coverage is 82.24%. Comparing base (724f450) to head (69037b0).
Report is 14 commits behind head on master.

Files with missing lines Patch % Lines
internal/testutils/proxyserver/proxyserver.go 78.37% 12 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #7881      +/-   ##
==========================================
- Coverage   82.28%   82.24%   -0.05%     
==========================================
  Files         381      385       +4     
  Lines       38539    38825     +286     
==========================================
+ Hits        31712    31931     +219     
- Misses       5535     5583      +48     
- Partials     1292     1311      +19     
Files with missing lines Coverage Δ
clientconn.go 92.45% <100.00%> (-0.33%) ⬇️
dialoptions.go 90.80% <100.00%> (+1.82%) ⬆️
internal/proxyattributes/proxyattributes.go 100.00% <ø> (ø)
.../resolver/delegatingresolver/delegatingresolver.go 77.77% <100.00%> (+8.91%) ⬆️
internal/transport/http2_client.go 91.47% <100.00%> (-0.23%) ⬇️
internal/transport/proxy.go 77.35% <100.00%> (+9.35%) ⬆️
internal/transport/transport.go 91.56% <ø> (+6.82%) ⬆️
resolver_wrapper.go 84.16% <100.00%> (+1.28%) ⬆️
internal/testutils/proxyserver/proxyserver.go 78.37% <78.37%> (ø)

... and 31 files with indirect coverage changes

}

// NewProxyServer create and starts a proxy server.
func NewProxyServer(lis net.Listener, requestCheck func(*http.Request) error, errCh chan error, doneCh chan struct{}, backendAddr string, resolutionOnClient bool, proxyServerStarted func()) *ProxyServer {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be we can do something like this fake server in its own package

// Package fakeserver provides a fake implementation of the management server.
? wdyt?

New should only create the object and StartServer/Run should start the go routine to accept requests

address := addr.Addr

//if the ProxyConnectAddr is set in the aattribute, do a proxy dial.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: attribute

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

internal/transport/proxy.go Show resolved Hide resolved
func proxyDial(ctx context.Context, addr string, grpcUA string) (net.Conn, error) {
newAddr := addr
proxyURL, err := mapAddress(addr)
// proxyDial dials, connecting to a proxy first if necessary. Dials, does the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove the necessary part now? It will always dial proxy if called?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

test/proxy_test.go Outdated Show resolved Hide resolved
test/proxy_test.go Outdated Show resolved Hide resolved
test/proxy_test.go Outdated Show resolved Hide resolved
@arjan-bal arjan-bal self-requested a review December 5, 2024 07:31
@arjan-bal arjan-bal self-assigned this Dec 5, 2024
@purnesh42H purnesh42H modified the milestones: 1.69 Release, 1.70 Release Dec 5, 2024
internal/proxyattributes/proxyattributes_test.go Outdated Show resolved Hide resolved
internal/proxyattributes/proxyattributes_test.go Outdated Show resolved Hide resolved
Addr: "test-address",
Attributes: attributes.New(userAndConnectAddrKey, attr{user: nil, addr: "proxy-address"}),
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nix new line

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

test/proxy_test.go Outdated Show resolved Hide resolved
test/proxy_test.go Outdated Show resolved Hide resolved

// Tests grpc.NewClient with the default "dns" resolver and dial option
// enabling target resolution on the client and verifies that the resolution
// happens on client.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: mention the dial option WithTargetResolutionEnabled()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

test/proxy_test.go Outdated Show resolved Hide resolved

// Tests grpc.NewClient with grpc.WithNoProxy() set and verifies that it does
// not dail to proxy, but directly to backend.
func (s) TestGrpcNewClientWithNoProxy(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the NoProxy and CustomDialer case, should we still override overrideHTTPSProxyFromEnvironment? That will make sure that even though proxy env is set we should skip it because of NoProxy and CustomDialer or is that not intended?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If WithNoProxy or WithContextDialer are set, the HTTPSProxyFromEnviornment function will never be called, but we can still override and add error if it is being called. Is it a good practice to do so? @purnesh42H

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah so right now if HTTPSProxyFromEnviornment is not present, it doesn't matter if WithNoProxy or WithContextDialer is set? The resolution will happen at client anyways? So having it present and not being called in these two cases is what we want to verify.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
wantProxyAuthStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+password))
if got := req.Header.Get("Proxy-Authorization"); got != wantProxyAuthStr {
gotDecoded, _ := base64.StdEncoding.DecodeString(got)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not ignore decoding errors. it should return error if decoding fails

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

internal/transport/proxy.go Show resolved Hide resolved
@arjan-bal arjan-bal removed their assignment Jan 10, 2025
@arjan-bal arjan-bal changed the title *: Correct gRFC A1 implementation *: Integrate delegating resolver and introduce dial options for target host resolution Jan 10, 2025
@arjan-bal arjan-bal changed the title *: Integrate delegating resolver and introduce dial options for target host resolution transport,grpc: Integrate delegating resolver and introduce dial options for target host resolution Jan 10, 2025
@dfawley dfawley assigned dfawley and unassigned easwars Jan 10, 2025
@dfawley dfawley requested review from dfawley and removed request for easwars January 10, 2025 18:23
clientconn.go Outdated Show resolved Hide resolved
dialoptions.go Outdated Show resolved Hide resolved
internal/transport/http2_client.go Outdated Show resolved Hide resolved
internal/transport/testutils/testutils.go Outdated Show resolved Hide resolved
resolver_wrapper.go Outdated Show resolved Hide resolved
@dfawley dfawley removed their assignment Jan 10, 2025
internal/testutils/proxy.go Outdated Show resolved Hide resolved
internal/testutils/proxy.go Outdated Show resolved Hide resolved
internal/testutils/proxy.go Outdated Show resolved Hide resolved
internal/testutils/proxy.go Outdated Show resolved Hide resolved
}()
t.Logf("Started proxy at: %q", pLis.Addr())
t.Cleanup(p.stop)
p.Addr = fmt.Sprintf("localhost:%d", ParsePort(t, pLis.Addr().String()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why can't this just be pLis.Addr().String()? There should be a comment explaining why it's doing all this extra stuff to seemingly just convert localhost:123 into localhost:123.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pLis..Addr() will be a IP address, we convert it to localhost:port to make sure the DNS resolver is working correctly for the proxy and connecting to the proxy server.

u := t.Username()
p, _ := t.Password()

if user := opts.User; user != (url.Userinfo{}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparison with a zero-initialized Userinfo looks a little off to me. Should this field in opts be a pointer instead to determine whether it was set intentionally?

Or should it check if either Username() or Password() are non-empty?

I'm not sure exactly what the right behavior is, but this seems questionable, as it's including any unexported fields in the comparison.

Related but potential existing problem to investigate: what if username is set but password is not? Is that valid? If so, is the proper behavior to encode it as username:, as it does currently, or should the : be omitted?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RFC doesn't clearly mention what is the correct syntax if password is absent , so I think either way should be fine, but adding a semicolon like username: would be more explicit in indication username has ended and the password is empty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a UserSet option as changing it to a pointer can allow the caller to modify the use info.

@dfawley dfawley assigned eshitachandwani and unassigned dfawley Jan 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NewClient functions behaviour is incompatible with secure forward-proxies
5 participants